#+TITLE: Org mode add cookie for direct children count
#+AUTHOR: Zelphir Kaltstahl
#+STARTUP: content
#+STARTUP: indent
#+STARTUP: align
#+STARTUP: inlineimages
#+STARTUP: hideblocks
#+STARTUP: entitiesplain
#+STARTUP: nologdone
#+STARTUP: nologreschedule
#+STARTUP: nologredeadline
#+STARTUP: nologrefile
#+TODO: TODO INPROGRESS | DONE CANCELLED
#+DATE: [2023-01-31 Di]
#+LANGUAGE: English
#+PRIORITIES: A E E
#+KEYWORDS: config emacs org-mode cookie count

* About

This snippet uses a package that makes it easy to add custom cookies to org mode headings.

* Configuration

#+begin_src elisp
(use-package org-custom-cookies
  :ensure t
  :after org
  :bind (:map org-mode-map
              ("C-c #" . (lambda (all) (interactive "P")
                           (progn ;; (org-update-statistics-cookies all)
                                  (org-custom-cookies-update-containing-subtree)))))
  :config
  (add-hook 'org-clock-out-hook 'org-custom-cookies-update-containing-subtree)
  (add-hook 'org-property-changed-functions
            (lambda(name value)
              (when (string-equal name "Effort")
                (org-custom-cookies-update-containing-subtree))))
  ;; Whenever a new heading is inserted, update this subtree's
  ;; cookies.
  (add-hook 'org-insert-heading-hook
            #'org-custom-cookies-update-containing-subtree))


;; Taken from https://emacs.stackexchange.com/questions/53295/how-to-count-direct-or-indirect-children-in-org-or-outline-mode/53299#53299
;; Comments added and slightly modified.
(defun org-custom-cookies--count-subheadings ()
  "Return the number of direct children in the current heading.
Note that this function modifies match data."
  (interactive)
  (save-excursion
    (let ((sum 0) (initial-heading-level (org-current-level)))
      (when (and (org-at-heading-p)
                 ;; Make sure we go from parent to child. And
                 ;; indirectly ensure that we're not on the same
                 ;; heading (meaning that there are no more headings
                 ;; after this one).  The initial heading is on a
                 ;; higher level, so its level number is lower ;)
                 (< initial-heading-level
                    (progn (outline-next-heading)
                           (org-outline-level))))
        ;; We already moved to the first child, so count that child.
        (setf sum 1)
        (while (condition-case nil
                   ;; Go forward 1 heading, but only on the same
                   ;; level. Probably raises an exception, if not
                   ;; possible, for example due to being at the last
                   ;; heading on this level. If it was possible to go
                   ;; 1 heading further, do the next thing, which is
                   ;; returning the value t.
                   (progn (outline-forward-same-level 1) t)
                 ;; Match any errors and if they happen, run `nil',
                 ;; which is `nil'. This makes the condition `nil',
                 ;; stopping iterations.
                 (error nil))
          ;; Count the headings.
          (setf sum (+ sum 1))))
      ;; Return the count of headings.
      sum)))


(defun org-custom-cookies--subtree-count-cookie ()
  "Return the number of sub headings in a subtree as a cookie string."
  (format "[#%s]"
          (org-custom-cookies--count-subheadings)))


(add-to-list 'org-custom-cookies-alist
             ;; The keys for the alist are the regexes to match in a
             ;; heading.
             '("\\[#[0-9]*\\]"
               ;; When a regex matches, the corresponding function is
               ;; called at the beginning of the matching header.
               . org-custom-cookies--subtree-count-cookie))
#+end_src
